import { KBinary } from "../binary/KBinary";
import { KWriteOp } from "../binary/KWriteOp";

const b = new KBinary();
const _keyMap = new Map<string, number>();

/** 对象编解码 */
export const ObjCoder = {

    /** any类型编码 */
    encode(val: any, bin?: KBinary, keyMap = _keyMap) {
        const buildKeys = keyMap === _keyMap;
        bin = bin || b.wStart();
        enAny(bin, val, buildKeys ? [] : null, keyMap, buildKeys);
        return bin;
    },

    /** {}对象类型编码 */
    encodeObj(obj: any, bin?: KBinary, keyMap = _keyMap) {
        const buildKeys = keyMap === _keyMap;
        bin = bin || b.wStart();
        enObj(bin, obj, buildKeys ? [] : null, keyMap, buildKeys);
        return bin;
    },

    /** []数组类型编码 */
    encodeArr(arr: any[], bin?: KBinary, keyMap = _keyMap) {
        const buildKeys = keyMap === _keyMap;
        bin = bin || b.wStart();
        enArr(bin, arr, buildKeys ? [] : null, keyMap, buildKeys);
        return bin;
    },

    enAny, enObj, enArr,

    /** any类型解码 */
    decode(bufOrBin: Uint8Array | KBinary, keys?: string[]) {
        const bin = bufOrBin instanceof Uint8Array ? b.rStart(bufOrBin) : bufOrBin;
        const any = bin.rHasNext ? deAny(bin, keys) : undefined;
        b.rEnd();
        return any;
    },

    /** {}对象类型解码 */
    decodeObj(bufOrBin: Uint8Array | KBinary, keys?: string[]) {
        const bin = bufOrBin instanceof Uint8Array ? b.rStart(bufOrBin) : bufOrBin;
        const obj = bin.rHasNext ? deObj(bin, keys) : undefined;
        b.rEnd();
        return obj;
    },

    /** []数组类型解码 */
    decodeArr(bufOrBin: Uint8Array | KBinary, keys?: string[]) {
        const bin = bufOrBin instanceof Uint8Array ? b.rStart(bufOrBin) : bufOrBin;
        const arr = bin.rHasNext ? deArr(bin, keys) : undefined;
        b.rEnd();
        return arr;
    },

    deAny, deObj, deArr
}

/** 值类型 */
const enum ValueType {
    /** 终止标识 */
    end,
    undefined,
    null,
    false,
    true,
    bytes,
    zero,
    uint8,
    uint16,
    uint32,
    uint64,
    nint8,
    nint16,
    nint32,
    nint64,
    ufloat0_32,
    ufloat0_64,
    ufloat32_32,
    ufloat32_64,
    ufloat64_32,
    nfloat0_32,
    nfloat0_64,
    nfloat32_32,
    nfloat32_64,
    nfloat64_32,
    numStr,
    e_str,
    str,
    e_obj,
    obj,
    e_arr,
    arr
};

// ============ ENCODE ============

function enKeys(b: KBinary, keys: string[]) {
    b.wAddOp(KWriteOp.strs, keys, 0);
}

function enKeysEnd(b: KBinary, keys: string[]) {
    b.wLen += b.getUintLen(keys.length);
    for (let i = 0; i < keys.length; ++i) {
        const key = keys[i];
        b.wLen += b.getUintLen(key.length) + b.getStrLen(key);
    }
    _keyMap.clear();
}

/** 编码any类型对象值 */
function enAny(b: KBinary, val: any, keys: string[] = [], keyMap = _keyMap, buildKeys = true) {
    if (buildKeys) {
        enKeys(b, keys);
    }
    //[写入] >>> 值类型 值(有的话)
    switch (typeof val) {
        case "undefined":
            b.wByte(ValueType.undefined);
            break;
        case "boolean":
            b.wByte(val ? ValueType.true : ValueType.false);
            break;
        case "number":
            if (val) {
                if (val <= Number.MAX_SAFE_INTEGER) {
                    const sign = val >= 0;
                    const v = val = Math.abs(val);
                    if (v % 1 === 0) {
                        if (v <= 0xff) {
                            b.wByte(sign ? ValueType.uint8 : ValueType.nint8).wUint8(v);
                        } else if (v <= 0xffff) {
                            b.wByte(sign ? ValueType.uint16 : ValueType.nint16).wUint16(v);
                        } else if (v <= 0xffffffff) {
                            b.wByte(sign ? ValueType.uint32 : ValueType.nint32).wUint32(v);
                        } else {
                            b.wByte(sign ? ValueType.uint64 : ValueType.nint64).wUint64(v);
                        }
                    } else {
                        const left = Math.floor(v);
                        const rightStr = v.toString().split(".")[1];
                        const right = parseInt(rightStr);
                        if (left === 0) {
                            if (right <= 0xffffffff) {
                                b.wByte(sign ? ValueType.ufloat0_32 : ValueType.nfloat0_32).wAuint32(right);
                            } else {
                                b.wByte(sign ? ValueType.ufloat0_64 : ValueType.nfloat0_64).wAuint64(right);
                            }
                        } else if (left <= 0xffffffff) {
                            if (right <= 0xffffffff) {
                                b.wByte(sign ? ValueType.ufloat32_64 : ValueType.nfloat32_64).wAuint32(left).wAuint32(right);
                            } else {
                                b.wByte(sign ? ValueType.ufloat32_64 : ValueType.nfloat32_64).wAuint32(left).wAuint64(right);
                            }
                        } else {
                            b.wByte(sign ? ValueType.ufloat64_32 : ValueType.nfloat64_32).wAuint64(left).wAuint32(right);
                        }
                        b.wByte(rightStr.length);
                    }
                } else {
                    b.wByte(ValueType.numStr).wStr(val.toString());
                }
            } else {
                b.wByte(ValueType.zero);
            }
            break;
        case "string":
            if (val) {
                b.wByte(ValueType.str).wStr(val);
            } else {
                b.wByte(ValueType.e_str);
            }
            break;
        case "object":
            if (val == null) {
                b.wByte(ValueType.null);
            } else if (val instanceof Array) {
                if (val.length) {
                    b.wByte(ValueType.arr);
                    enArr(b, val, keys, keyMap, false);
                } else {
                    b.wByte(ValueType.e_arr);
                }
            } else if (val instanceof Uint8Array) {
                b.wByte(ValueType.bytes).wBytes(val);
            } else {
                let field: string;
                for (field in val) {
                    b.wByte(ValueType.obj);
                    enObj(b, val, keys, keyMap, false);
                    break;
                }
                if (!field) b.wByte(ValueType.e_obj);
            }
            break;
    }
    if (buildKeys) {
        enKeysEnd(b, keys);
    }
}

/** 编码{}对象类型 */
function enObj(b: KBinary, obj: any, keys: string[] = [], keyMap = _keyMap, buildKeys = true) {
    if (buildKeys) {
        enKeys(b, keys);
    }
    for (const field in obj) {
        if (obj[field] === undefined) continue;
        //[写入] >>> 值 key(idx)
        enAny(b, obj[field], keys, keyMap, false);
        if (!keyMap.has(field)) {
            keyMap.set(field, keys.length);
            keys.push(field);
        }
        b.wAuint32(keyMap.get(field));
    }
    //[写入] >>> 终止标识
    b.wByte(ValueType.end);
    if (buildKeys) {
        enKeysEnd(b, keys);
    }
}

/** 编码[]数组类型 */
function enArr(b: KBinary, arr: any[], keys: string[] = [], keyMap = _keyMap, buildKeys = true) {
    if (buildKeys) {
        enKeys(b, keys);
    }
    //[写入] >>> 数组长度 值
    b.wAuint32(arr.length);
    for (let i = 0; i < arr.length; ++i) {
        enAny(b, arr[i], keys, keyMap, false);
    }
    if (buildKeys) {
        enKeysEnd(b, keys);
    }
}

// ============ DECODE ============

function deKeys(b: KBinary) {
    const keys: string[] = [];
    let len = b.rAuint32();
    while (--len >= 0) {
        keys.push(b.rStr());
    }
    return keys;
}

/** 解码{}对象类型对象值 */
function deAny(b: KBinary, keys?: string[]): any {
    if (!keys) {
        keys = deKeys(b);
    }

    //[读取] >>> 值类型 对象值
    switch (b.rByte()) {
        default: return undefined;
        case ValueType.null: return null;
        case ValueType.true: return true;
        case ValueType.false: return false;
        case ValueType.bytes: return b.rBytes();
        case ValueType.zero: return 0;
        case ValueType.uint8: return b.rUint8();
        case ValueType.uint16: return b.rUint16();
        case ValueType.uint32: return b.rUint32();
        case ValueType.uint64: return b.rUint64();
        case ValueType.nint8: return -b.rUint8();
        case ValueType.nint16: return -b.rUint16();
        case ValueType.nint32: return -b.rUint32();
        case ValueType.nint64: return -b.rUint64();
        case ValueType.ufloat0_32: return b.rAuint32() / Math.pow(10, b.rByte());
        case ValueType.ufloat0_64: return b.rAuint64() / Math.pow(10, b.rByte());
        case ValueType.ufloat32_32: return b.rAuint32() + b.rAuint32() / Math.pow(10, b.rByte());
        case ValueType.ufloat32_64: return b.rAuint32() + b.rAuint64() / Math.pow(10, b.rByte());
        case ValueType.ufloat64_32: return b.rAuint64() + b.rAuint32() / Math.pow(10, b.rByte());
        case ValueType.nfloat0_32: return -(b.rAuint32() / Math.pow(10, b.rByte()));
        case ValueType.nfloat0_64: return -(b.rAuint64() / Math.pow(10, b.rByte()));
        case ValueType.nfloat32_32: return -(b.rAuint32() + b.rAuint32() / Math.pow(10, b.rByte()));
        case ValueType.nfloat32_64: return -(b.rAuint32() + b.rAuint64() / Math.pow(10, b.rByte()));
        case ValueType.nfloat64_32: return -(b.rAuint64() + b.rAuint32() / Math.pow(10, b.rByte()));
        case ValueType.numStr: return Number(b.rStr());
        case ValueType.e_str: return "";
        case ValueType.str: return b.rStr();
        case ValueType.e_obj: return {};
        case ValueType.obj: return deObj(b, keys);
        case ValueType.e_arr: return [];
        case ValueType.arr: return deArr(b, keys);
    }
}

/** 解码{}对象类型 */
function deObj(b: KBinary, keys?: string[]) {
    if (!keys) {
        keys = deKeys(b);
    }

    const obj: any = {};
    while (b.rHasNext) {
        //[读取] >>> 值 key
        const val = deAny(b, keys);
        if (val === undefined) break; //终止符
        obj[keys[b.rAuint32()]] = val;
    }
    return obj;
}

/** 解码[]数组类型 */
function deArr(b: KBinary, keys?: string[]) {
    if (!keys) {
        keys = deKeys(b);
    }

    const arr = [];
    //[读取] >>> 数组长度 值
    let len = b.rAuint32();
    while (--len >= 0) {
        arr.push(deAny(b, keys));
    }
    return arr;
}